package com.nulldev.util.web.HttpServer3.extensions.nCaptcha.backend.user;

import java.util.List;
import java.util.Map;

import com.nulldev.util.VariableAPI.ArrayUtils;
import com.nulldev.util.VariableAPI.MathUtil;
import com.nulldev.util.VariableAPI.StringsUtil;
import com.nulldev.util.data.Variables;
import com.nulldev.util.data.Arrays.maps.FastMaps;
import com.nulldev.util.internal.backport.concurrency9.Lists;
import com.nulldev.util.security.PasswordGenerator.PasswordGenerator;
import com.nulldev.util.security.encryptAPI.Encryptors;
import com.nulldev.util.security.encryptAPI.Encryptors.SystemEncryptors;
import com.nulldev.util.security.encryptAPI.impls.xxtea.XXTEACipher;
import com.nulldev.util.web.HttpServer3.extensions.nCaptcha.captchas.auto.math.MathChallengeGenerator;
import com.nulldev.util.web.HttpServer3.extensions.nCaptcha.captchas.auto.math.MathChallengeGenerator.MathChallengeContext;

public class User implements UserInfoType {
	private final Map<String, Object> USER_INFO = FastMaps.map(8);

	/**
	 * Level 0 = Low, not a bot. <br>
	 * Level 1 = Medium, seems to be special. <br>
	 * Level 2 = High, something is wrong. <br>
	 * Level 3 = Bot, prevent user captcha for up to an hour. <br>
	 */
	private int _captcha_level = 0;
	private long expiry = (System.currentTimeMillis() + 3600000);

	public User(final String uuid) {
		this.USER_INFO.putIfAbsent(USER_INFO_TYPE_UUID, uuid);
		this.USER_INFO.putIfAbsent(USER_INFO_TYPE_KEY,
				(new PasswordGenerator.PasswordGeneratorBuilder().useDigits(true).useLower(true).usePunctuation(true).useUpper(true).build().generate(48)));
	}

	public MathChallengeContext getMathChallengeContext() {
		if (!this.USER_INFO.containsKey(USER_INFO_TYPE_MATH_CHALLENGE_PAYLOAD)) {
			final MathChallengeContext ctx = MathChallengeGenerator.generate();
			this.USER_INFO.putIfAbsent(USER_INFO_TYPE_MATH_CHALLENGE_PAYLOAD, ctx);
			return ctx;
		} else {
			return (MathChallengeContext) this.USER_INFO.get(USER_INFO_TYPE_MATH_CHALLENGE_PAYLOAD);
		}
	}

	public Object getUserInfoObject(final String object) {
		return this.USER_INFO.get(object);
	}

	public void captchaFailed() {
		this._captcha_level = MathUtil.clamp(0, 3, this._captcha_level + 1);
		this._captcha_finished = false;
	}

	public void captchaPassed() {
		this._captcha_level = MathUtil.clamp(0, 3, this._captcha_level - 1);
		this._captcha_finished = true;
	}

	public boolean isNotBot() {
		return this._captcha_level < 3;
	}

	public int getLevel() {
		return this._captcha_level;
	}

	private volatile String[] expectedResults = new String[0];

	public void setExpectedResults(final String[] result) {
		Variables.requireNonNull(result, "result");
		this.expectedResults = result;
	}

	public String[] getExpectedResults() {
		return this.expectedResults;
	}

	public boolean validExpectedResult(final String input) {
		if (input == null || StringsUtil.isBlank(input)) {
			return false;
		}
		return ArrayUtils.StringArrayUtils.containsString(this.expectedResults, input);
	}

	public void resetCaptchaStatus() {
		this._captcha_level = 0;
	}

	private boolean _captcha_finished;

	public boolean isExpired() {
		return System.currentTimeMillis() > this.expiry;
	}

	public boolean captchaDone() {
		return this._captcha_finished;
	}

	public long time() {
		return this.expiry - System.currentTimeMillis();
	}

	public String key() {
		return (String) this.USER_INFO.get(USER_INFO_TYPE_KEY);
	}

	public String uuid() {
		return (String) this.USER_INFO.get(USER_INFO_TYPE_UUID);
	}

	public enum UserCipher {
		BLOWFISH("blowfish"),
		XXTEA("xtea"),

		;

		private static final List<UserCipher> ENABLED_CIPHERS = Lists.of(BLOWFISH, XXTEA);

		private final String format;

		private UserCipher(final String format) {
			this.format = format;
		}

		public static UserCipher get() {
			// TODO: RC4 seems to be broken...
			return ArrayUtils.random(ENABLED_CIPHERS);
		}

		public String format() {
			return this.format;
		}
	}

	private final UserCipher c = UserCipher.get();

	public UserCipher getCipher() {
		return this.c;
	}

	public String encryptPayload(final byte[] payload) {
		Variables.requireNonNull(payload, "payload");
		final String userKey = (String) this.USER_INFO.get(USER_INFO_TYPE_KEY);
		switch (c) {
			case XXTEA:
				return XXTEACipher.encryptToBase64String(payload, userKey);
			case BLOWFISH:
				return Encryptors.encryptorFor(SystemEncryptors.Blowfish, userKey).encrypt(payload);
			default:
				throw new UnsupportedOperationException("Can't encrypt: " + c);
		}
	}

	public String encryptPayload(final String payload) {
		Variables.requireNonNull(payload, "payload");
		final String userKey = (String) this.USER_INFO.get(USER_INFO_TYPE_KEY);
		switch (c) {
			case XXTEA:
				return XXTEACipher.encryptToBase64String(payload, userKey);
			case BLOWFISH:
				return Encryptors.encryptorFor(SystemEncryptors.Blowfish, userKey).encrypt(payload);
			default:
				throw new UnsupportedOperationException("Can't encrypt: " + c);
		}
	}

	public String decryptPayload(final String payload) {
		Variables.requireNonNull(payload, "payload");
		final String userKey = (String) this.USER_INFO.get(USER_INFO_TYPE_KEY);
		switch (c) {
			case XXTEA:
				return XXTEACipher.decryptBase64StringToString(payload, userKey);
			case BLOWFISH:
				return Encryptors.encryptorFor(SystemEncryptors.Blowfish, userKey).decrypt(payload);
			default:
				throw new UnsupportedOperationException("Can't decrypt: " + c);
		}
	}

	public void markAsBot() {
		this._captcha_level = 3;
	}

	public Object removeInfoObject(final String userInfo) {
		return this.USER_INFO.remove(userInfo);
	}

	public boolean hasInfoObject(final String userInfo) {
		return this.USER_INFO.containsKey(userInfo);
	}

	public Object putUserInfo(final String userInfo, final Object value) {
		return this.USER_INFO.put(userInfo, value);
	}
}
